#include "reedsolomon.h"
#include "ui_reedsolomon.h"

ReedSolomon::ReedSolomon(qlonglong n, qlonglong k, QWidget *parent) :
  QMainWindow(parent),
  ui(new Ui::ReedSolomon),
  m_n(n),
  m_k(k),
  m_gf(37),
  m_encoder(n, k),
  m_decoder(n, k),
  m_modulator(n+1, 4),
  m_demodulator(n+1, 4),
  m_channel(4, 0),
  m_calcGraph(false)
{
  ui->setupUi(this);

#ifdef ERRS
  if ( n <= 0 || k <= 0 || n <= k ) {
    error("ReedSolomon::ReedSolomon(): код задан некорректно.");
  }
#endif

  // инициализировать параметры графика Pe(SNR)
  m_snrBeg  = ui->snrBeg->value();
  m_snrEnd  = ui->snrEnd->value();
  m_snrStep = ui->snrStep->value();
  m_limErrs = ui->limErrs->value();

  // задать файлам имена
  m_outFileSource.setFileName("source.txt");
  m_outFileEncoder.setFileName("encoder.txt");
  m_outFileDecoder.setFileName("decoder.txt");
  m_outFileModulator.setFileName("modulator.txt");
  m_outFileDemodulator.setFileName("demodulator.txt");
  m_outFileChannel.setFileName("channel.txt");
  m_outFileGraph.setFileName("graph.m");

  // нужно ли писать в файлы
  m_outFlag = ui->outFlag->isChecked();

  // !!!!!!!!!!! передать поле на отображение виджету
  createGui();

  // СВЯЗЬ СИГНАЛОВ И СЛОТОВ
  // !!!!!!!!!!!!! возможно можно обойтись сигналом editingFinished()
  // синхронизировать параметры графика Pe(SNR)
  connect(  ui->snrBeg, SIGNAL( valueChanged(int) ), SLOT( slotSnrBeg(int) )  );
  connect(  ui->snrEnd, SIGNAL( valueChanged(int) ), SLOT( slotSnrEnd(int) )  );
  connect(  ui->snrStep, SIGNAL( valueChanged(int) ), SLOT( slotSnrStep(int) )  );
  connect(  ui->limErrs, SIGNAL( valueChanged(int) ), SLOT( slotLimErrs(int) )  );

  connect(  ui->outFlag, SIGNAL( toggled(bool) ), SLOT( slotOutFlag(bool) )  );
}

// событие закрытия окна
void ReedSolomon::closeEvent(QCloseEvent *e) { /*virtual*/
  // перед закрытием остановить расчеты
  slotAtionGraph();

  m_gfViewer.close(); // закрыть и поле тоже

  QMainWindow::closeEvent(e);
}

// создание графического интерфейса
void ReedSolomon::createGui() {
  // настроить строку состояния
  ui->statusBar->addWidget( new QLabel("<p>&copy; ГУАП, группа 5712, " // copyright
                                       "Орлов Н. А.</p>") );

  // настроить панель инструментов
  ui->mainToolBar->setMovable(false); // запрет перемещения мышью
  // кнопка показа поля
  m_ationGf    = ui->mainToolBar->addAction(QIcon(":/gf"), "показать поле",
                                            this, SLOT( slotViewGF() ) );
  // кнопка расчета графика графика
  m_ationGraph = ui->mainToolBar->addAction(QIcon(":/graph"), "расчитать график",
                                            this, SLOT( slotCalcGraph() ) );
  m_ationGraph->setCheckable(true);
}

ReedSolomon::~ReedSolomon()
{
  delete ui;
}

// открыть файлы всех блоков системы передачи
// постусловие: файлы должны открыться
void ReedSolomon::openOutFiles() {
  if ( !m_outFileSource.open(QIODevice::WriteOnly) ||
       !m_outFileEncoder.open(QIODevice::WriteOnly) ||
       !m_outFileDecoder.open(QIODevice::WriteOnly) ||
       !m_outFileModulator.open(QIODevice::WriteOnly) ||
       !m_outFileDemodulator.open(QIODevice::WriteOnly) ||
       !m_outFileChannel.open(QIODevice::WriteOnly) ||
       !m_outFileGraph.open(QIODevice::WriteOnly) ) {
    error("ReedSolomon::openOutFiles(): ошибка открытия файла для записи.");
  }
}

// закрыть файлы всех блоков системы передачи
void ReedSolomon::closeOutFiles() {
  m_outFileSource.close();
  m_outFileEncoder.close();
  m_outFileDecoder.close();
  m_outFileModulator.close();
  m_outFileDemodulator.close();
  m_outFileChannel.close();
  m_outFileGraph.close();
}

// открыть файл с точками графика
// постусловие: файлы должны открыться
void ReedSolomon::openOutFileGraph() {
  if ( !m_outFileGraph.open(QIODevice::WriteOnly) ) {
    error("ReedSolomon::openOutFiles(): ошибка открытия файла для записи.");
  }
}

// закрыть файл с точками графика
void ReedSolomon::closeOutFileGraph() {
  m_outFileGraph.close();
}

// запись полинома в файл
// предусловие: входной вектор должен быть не пуст
void ReedSolomon::log(QVector<qlonglong> poly, QFile *file) {
#ifdef ERRS
  if ( poly.size() == 0 ) { // проверка предусловия
    error("ReedSolomon::log(): входной вектор пуст.");
  }
#endif
  QTextStream out(file);

  poly.at(0)? out << "(" << m_gf.log( poly.at(0) ) << ")" << "[0]" :
              out << "(-)[0]";
  for (qlonglong i = 1; i < poly.size(); ++i) {
    poly.at(i)? out << " + (" << m_gf.log( poly.at(i) ) << ")" << "[" << i << "]" :
                out << " + (-)" << "[" << i << "]";
  }
  out << "\n\n";
}

// запись точек в файл
// предусловие: входной вектор должен быть не пуст
void ReedSolomon::log(QVector<QPointF> poly, QFile *file) {
#ifdef ERRS
  if ( poly.size() == 0 ) { // проверка предусловия
    error("ReedSolomon::log(): входной вектор пуст.");
  }
#endif
  QTextStream out(file);

  out << "(" << poly.at(0).x() << "; " << poly.at(0).y() << ")";
  for (qlonglong i = 1; i < poly.size(); ++i) {
    out << ", (" << poly.at(i).x() << "; " << poly.at(i).y() << ")";
  }
  out << "\n\n";
}

// запись точек графика в файл
// предусловие: входной вектор должен быть не пуст
void ReedSolomon::logGraph(QVector<QPointF> poly, QFile *file) {
#ifdef ERRS
  if ( poly.size() == 0 ) { // проверка предусловия
    error("ReedSolomon::log(): входной вектор пуст.");
  }
#endif
  QTextStream out(file);

  out << "SNR_Pe = [\n\t\t\t[" << poly.at(0).x() << "; " << poly.at(0).y() << "]";
  for (qlonglong i = 1; i < poly.size(); ++i) {
    out << ",...\n\t\t\t[" << poly.at(i).x() << "; " << poly.at(i).y() << "]";
  }
  out << "\t];\nsemilogy(SNR_Pe(1,:), SNR_Pe(2,:), 'k-o')\n"
         "title('P_{e}(E/N_{o})')\n"
         "xlabel('E/N_{o}, dB')\n"
         "ylabel('P_{e}')\n"
         "legend('RS(" + QString::number(m_n) +
                  ", " + QString::number(m_k) +
                  ", "  + QString::number(m_n-m_k+1) +
                  ")', 0)\n"
         "grid on";
}

// запись в файлы системы передачи заголовка SNR
void ReedSolomon::generalLog(QString str) {
  m_outFileSource.write( str.toUtf8() );
  m_outFileEncoder.write( str.toUtf8() );
  m_outFileModulator.write( str.toUtf8() );
  m_outFileChannel.write( str.toUtf8() );
  m_outFileDemodulator.write( str.toUtf8() );
  m_outFileDecoder.write( str.toUtf8() );
}

// расчет графика Pe(SNR)
QVector<QPointF> ReedSolomon::calcGraph() {
  QVector<QPointF> res; // точки графика

  if (m_outFlag) { // документировать?
    openOutFiles(); // открыть файлы всех блоков системы передачи
  }
  else {
    openOutFileGraph(); // открыть файл с точками графика
  }

  QString skelGeneralLog = "====================================================="
                           "===========================\n"
                           " SNR: %1 дБ\n"
                           "====================================================="
                           "===========================\n\n";
  QString skelLabelProgress = "(SNR = %1 / %2)\nРасчет графика...";

  // индикатор прогресса
  QProgressDialog progress( skelLabelProgress.arg(m_snrBeg).arg(m_snrEnd),
                            "&Прервать", 0, m_limErrs );
  progress.setWindowTitle("Пожалуйста подождите");
  progress.setWindowIcon( QIcon(":/graph") );
  progress.setAutoClose(false);                    // не закрывать после 100%
  progress.setMinimumDuration(0);                  // показать без задержек
  progress.show();

  // при отмене имитировать отжатие кнопки расчета графика
  connect(  &progress, SIGNAL( canceled() ), SLOT( slotAtionGraph() )  );

  qlonglong nErrs;  // число совершенных ошибок
  qlonglong nTests; // число проведенных испытаний
  qreal     Pe;     // вычисленная вероятность ошибки
  for (int snr = m_snrBeg; snr <= m_snrEnd; snr += m_snrStep) {
    nErrs  = 0;
    nTests = 0;
    m_channel.setSNRdB(snr); // изменить SNR для канала

    if (m_outFlag) { // документировать?
      generalLog( skelGeneralLog.arg(snr) ); // записать в файлы общий заголовок SNR
    }

    // обновить заголовок индикатора прогресса
    progress.setLabelText( skelLabelProgress.arg(snr).arg(m_snrEnd) );

    while (nErrs < m_limErrs) {
      QVector<qlonglong> f  = m_gf.genPoly(m_k);             // сгенерировать
                                                             // информационный полином
      QVector<qlonglong> a  = m_encoder.code(f);             // кодировать
      QVector<QPointF>   s  = m_modulator.modulation(a);     // модулировать
      QVector<QPointF>   r  = m_channel.addNoise(s);         // добавить шум
      QVector<qlonglong> b  = m_demodulator.demodulation(r); // демодулировать

      QVector<qlonglong> f_ = m_decoder.decode(b);           // декодировать

      if (m_outFlag) { // документировать?
        log(f,  &m_outFileSource);
        log(a,  &m_outFileEncoder);
        log(s,  &m_outFileModulator);
        log(r,  &m_outFileChannel);
        log(b,  &m_outFileDemodulator);
        log(f_, &m_outFileDecoder);
      }

      if ( f != f_ ) { // проверка на ошибку декодирования
        progress.setValue( ++nErrs );
      }

      // предотвратить подвисание программы
      qApp->processEvents(); // обработка накопившихся событий

      ++nTests;

      if (!m_calcGraph) { // флаг остановки
        break;
      }
    }

    Pe = nErrs/(qreal)nTests; // вычислить вероятность ошибки

    res.append( QPointF(snr, Pe) ); // добавить очередную точку графика

    if (!m_calcGraph) { // флаг остановки
      break;
    }    
  }

  progress.close();

  logGraph(res, &m_outFileGraph);

  if (m_outFlag) { // документировать?
    closeOutFiles(); // закрыть файлы всех блоков системы передачи
  }
  else {
    closeOutFileGraph(); // закрыть файл с точками графика
  }

  return res;
}

// =======================================================================================
// СЛОТЫ
// =======================================================================================

// изменение нижней границы SNR
void ReedSolomon::slotSnrBeg(int val) {
  m_snrBeg = val;
}

// изменение верхней границы SNR
void ReedSolomon::slotSnrEnd(int val) {
  m_snrEnd = val;
}

// изменение числа ошибок для перехода к следующему SNR
void ReedSolomon::slotLimErrs(int val) {
  m_limErrs = val;
}

// изменение шага SNR
void ReedSolomon::slotSnrStep(int val) {
  m_snrStep = val;
}

// расчитать точки графика
void ReedSolomon::slotCalcGraph() {
  QVector<QPointF> points;

  m_calcGraph = m_ationGraph->isChecked();
  if (m_calcGraph) {
    m_ationGraph->setIcon( QIcon(":/cancel") );
    centralWidget()->setEnabled(false);
    points = calcGraph();
    m_ationGraph->setIcon( QIcon(":/graph") );
    centralWidget()->setEnabled(true);
    m_ationGraph->setChecked(false);
  }
  else {
    m_ationGraph->setIcon( QIcon(":/graph") );
    centralWidget()->setEnabled(true);
  }
}

// показать поле
void ReedSolomon::slotViewGF() {
  // центрировать
  m_gfViewer.move((QApplication::desktop()->width()  - m_gfViewer.width())  / 2,
                  (QApplication::desktop()->height() - m_gfViewer.height()) / 2);
  m_gfViewer.isHidden() ? m_gfViewer.show(): m_gfViewer.hide();
}

// вкл/выкл документирование
void ReedSolomon::slotOutFlag(bool flag) {
  m_outFlag = flag;
}

// имитировать отжатие кнопки расчета графика
void ReedSolomon::slotAtionGraph() {
  m_ationGraph->setChecked(false);
  slotCalcGraph();
}
